package net.ddns.xcyyds.kungfu.network;

import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.common.capabilities.CapabilityToken;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.Capability;

import net.minecraft.world.level.saveddata.SavedData;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.Entity;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.Direction;
import net.minecraft.client.Minecraft;

import net.ddns.xcyyds.kungfu.KungFuMod;

import java.util.function.Supplier;

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public class KungFuModVariables {
	@SubscribeEvent
	public static void init(FMLCommonSetupEvent event) {
		KungFuMod.addNetworkMessage(SavedDataSyncMessage.class, SavedDataSyncMessage::buffer, SavedDataSyncMessage::new,
				SavedDataSyncMessage::handler);
		KungFuMod.addNetworkMessage(PlayerVariablesSyncMessage.class, PlayerVariablesSyncMessage::buffer, PlayerVariablesSyncMessage::new,
				PlayerVariablesSyncMessage::handler);
	}

	@SubscribeEvent
	public static void init(RegisterCapabilitiesEvent event) {
		event.register(PlayerVariables.class);
	}

	@Mod.EventBusSubscriber
	public static class EventBusVariableHandlers {
		@SubscribeEvent
		public static void onPlayerLoggedInSyncPlayerVariables(PlayerEvent.PlayerLoggedInEvent event) {
			if (!event.getPlayer().level.isClientSide())
				((PlayerVariables) event.getPlayer().getCapability(PLAYER_VARIABLES_CAPABILITY, null).orElse(new PlayerVariables()))
						.syncPlayerVariables(event.getPlayer());
		}

		@SubscribeEvent
		public static void onPlayerRespawnedSyncPlayerVariables(PlayerEvent.PlayerRespawnEvent event) {
			if (!event.getPlayer().level.isClientSide())
				((PlayerVariables) event.getPlayer().getCapability(PLAYER_VARIABLES_CAPABILITY, null).orElse(new PlayerVariables()))
						.syncPlayerVariables(event.getPlayer());
		}

		@SubscribeEvent
		public static void onPlayerChangedDimensionSyncPlayerVariables(PlayerEvent.PlayerChangedDimensionEvent event) {
			if (!event.getPlayer().level.isClientSide())
				((PlayerVariables) event.getPlayer().getCapability(PLAYER_VARIABLES_CAPABILITY, null).orElse(new PlayerVariables()))
						.syncPlayerVariables(event.getPlayer());
		}

		@SubscribeEvent
		public static void clonePlayer(PlayerEvent.Clone event) {
			event.getOriginal().revive();
			PlayerVariables original = ((PlayerVariables) event.getOriginal().getCapability(PLAYER_VARIABLES_CAPABILITY, null)
					.orElse(new PlayerVariables()));
			PlayerVariables clone = ((PlayerVariables) event.getEntity().getCapability(PLAYER_VARIABLES_CAPABILITY, null)
					.orElse(new PlayerVariables()));
			clone.neiLi = original.neiLi;
			clone.neiLiMax = original.neiLiMax;
			clone.oneHandJian = original.oneHandJian;
			clone.oneHandJianLL = original.oneHandJianLL;
			clone.oneHandJianRR = original.oneHandJianRR;
			clone.oneHandJianRL = original.oneHandJianRL;
			clone.oneHandJianLR = original.oneHandJianLR;
			clone.howToUse = original.howToUse;
			clone.neiLiAdd = original.neiLiAdd;
			clone.shuLianDuLevel = original.shuLianDuLevel;
			clone.mouseOrKey = original.mouseOrKey;
			clone.qingGong = original.qingGong;
			clone.quanFa = original.quanFa;
			clone.quanFaLL = original.quanFaLL;
			clone.quanFaRR = original.quanFaRR;
			clone.quanFaRL = original.quanFaRL;
			clone.quanFaLR = original.quanFaLR;
			clone.NeiLiHud = original.NeiLiHud;
			if (!event.isWasDeath()) {
				clone.useing = original.useing;
			}
		}

		@SubscribeEvent
		public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
			if (!event.getPlayer().level.isClientSide()) {
				SavedData mapdata = MapVariables.get(event.getPlayer().level);
				SavedData worlddata = WorldVariables.get(event.getPlayer().level);
				if (mapdata != null)
					KungFuMod.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getPlayer()),
							new SavedDataSyncMessage(0, mapdata));
				if (worlddata != null)
					KungFuMod.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getPlayer()),
							new SavedDataSyncMessage(1, worlddata));
			}
		}

		@SubscribeEvent
		public static void onPlayerChangedDimension(PlayerEvent.PlayerChangedDimensionEvent event) {
			if (!event.getPlayer().level.isClientSide()) {
				SavedData worlddata = WorldVariables.get(event.getPlayer().level);
				if (worlddata != null)
					KungFuMod.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getPlayer()),
							new SavedDataSyncMessage(1, worlddata));
			}
		}
	}

	public static class WorldVariables extends SavedData {
		public static final String DATA_NAME = "kung_fu_worldvars";

		public static WorldVariables load(CompoundTag tag) {
			WorldVariables data = new WorldVariables();
			data.read(tag);
			return data;
		}

		public void read(CompoundTag nbt) {
		}

		@Override
		public CompoundTag save(CompoundTag nbt) {
			return nbt;
		}

		public void syncData(LevelAccessor world) {
			this.setDirty();
			if (world instanceof Level level && !level.isClientSide())
				KungFuMod.PACKET_HANDLER.send(PacketDistributor.DIMENSION.with(level::dimension), new SavedDataSyncMessage(1, this));
		}

		static WorldVariables clientSide = new WorldVariables();

		public static WorldVariables get(LevelAccessor world) {
			if (world instanceof ServerLevel level) {
				return level.getDataStorage().computeIfAbsent(e -> WorldVariables.load(e), WorldVariables::new, DATA_NAME);
			} else {
				return clientSide;
			}
		}
	}

	public static class MapVariables extends SavedData {
		public static final String DATA_NAME = "kung_fu_mapvars";
		public double neiLiMul = 0.012;
		public double sld1 = 100.0;
		public double sld2 = 400.0;
		public double sld3 = 1000.0;
		public double sld4 = 2000.0;

		public static MapVariables load(CompoundTag tag) {
			MapVariables data = new MapVariables();
			data.read(tag);
			return data;
		}

		public void read(CompoundTag nbt) {
			neiLiMul = nbt.getDouble("neiLiMul");
			sld1 = nbt.getDouble("sld1");
			sld2 = nbt.getDouble("sld2");
			sld3 = nbt.getDouble("sld3");
			sld4 = nbt.getDouble("sld4");
		}

		@Override
		public CompoundTag save(CompoundTag nbt) {
			nbt.putDouble("neiLiMul", neiLiMul);
			nbt.putDouble("sld1", sld1);
			nbt.putDouble("sld2", sld2);
			nbt.putDouble("sld3", sld3);
			nbt.putDouble("sld4", sld4);
			return nbt;
		}

		public void syncData(LevelAccessor world) {
			this.setDirty();
			if (world instanceof Level && !world.isClientSide())
				KungFuMod.PACKET_HANDLER.send(PacketDistributor.ALL.noArg(), new SavedDataSyncMessage(0, this));
		}

		static MapVariables clientSide = new MapVariables();

		public static MapVariables get(LevelAccessor world) {
			if (world instanceof ServerLevelAccessor serverLevelAcc) {
				return serverLevelAcc.getLevel().getServer().getLevel(Level.OVERWORLD).getDataStorage().computeIfAbsent(e -> MapVariables.load(e),
						MapVariables::new, DATA_NAME);
			} else {
				return clientSide;
			}
		}
	}

	public static class SavedDataSyncMessage {
		public int type;
		public SavedData data;

		public SavedDataSyncMessage(FriendlyByteBuf buffer) {
			this.type = buffer.readInt();
			this.data = this.type == 0 ? new MapVariables() : new WorldVariables();
			if (this.data instanceof MapVariables _mapvars)
				_mapvars.read(buffer.readNbt());
			else if (this.data instanceof WorldVariables _worldvars)
				_worldvars.read(buffer.readNbt());
		}

		public SavedDataSyncMessage(int type, SavedData data) {
			this.type = type;
			this.data = data;
		}

		public static void buffer(SavedDataSyncMessage message, FriendlyByteBuf buffer) {
			buffer.writeInt(message.type);
			buffer.writeNbt(message.data.save(new CompoundTag()));
		}

		public static void handler(SavedDataSyncMessage message, Supplier<NetworkEvent.Context> contextSupplier) {
			NetworkEvent.Context context = contextSupplier.get();
			context.enqueueWork(() -> {
				if (!context.getDirection().getReceptionSide().isServer()) {
					if (message.type == 0)
						MapVariables.clientSide = (MapVariables) message.data;
					else
						WorldVariables.clientSide = (WorldVariables) message.data;
				}
			});
			context.setPacketHandled(true);
		}
	}

	public static final Capability<PlayerVariables> PLAYER_VARIABLES_CAPABILITY = CapabilityManager.get(new CapabilityToken<PlayerVariables>() {
	});

	@Mod.EventBusSubscriber
	private static class PlayerVariablesProvider implements ICapabilitySerializable<Tag> {
		@SubscribeEvent
		public static void onAttachCapabilities(AttachCapabilitiesEvent<Entity> event) {
			if (event.getObject() instanceof Player && !(event.getObject() instanceof FakePlayer))
				event.addCapability(new ResourceLocation("kung_fu", "player_variables"), new PlayerVariablesProvider());
		}

		private final PlayerVariables playerVariables = new PlayerVariables();
		private final LazyOptional<PlayerVariables> instance = LazyOptional.of(() -> playerVariables);

		@Override
		public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
			return cap == PLAYER_VARIABLES_CAPABILITY ? instance.cast() : LazyOptional.empty();
		}

		@Override
		public Tag serializeNBT() {
			return playerVariables.writeNBT();
		}

		@Override
		public void deserializeNBT(Tag nbt) {
			playerVariables.readNBT(nbt);
		}
	}

	public static class PlayerVariables {
		public double neiLi = 0;
		public double neiLiMax = 12.0;
		public double oneHandJian = 0.0;
		public boolean oneHandJianLL = false;
		public boolean oneHandJianRR = false;
		public boolean oneHandJianRL = false;
		public boolean oneHandJianLR = false;
		public boolean howToUse = false;
		public boolean useing = false;
		public double neiLiAdd = 0.05;
		public double shuLianDuLevel = 0;
		public boolean mouseOrKey = false;
		public double qingGong = 0;
		public double quanFa = 0;
		public boolean quanFaLL = false;
		public boolean quanFaRR = false;
		public boolean quanFaRL = false;
		public boolean quanFaLR = false;
		public boolean NeiLiHud = true;

		public void syncPlayerVariables(Entity entity) {
			if (entity instanceof ServerPlayer serverPlayer)
				KungFuMod.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> serverPlayer), new PlayerVariablesSyncMessage(this));
		}

		public Tag writeNBT() {
			CompoundTag nbt = new CompoundTag();
			nbt.putDouble("neiLi", neiLi);
			nbt.putDouble("neiLiMax", neiLiMax);
			nbt.putDouble("oneHandJian", oneHandJian);
			nbt.putBoolean("oneHandJianLL", oneHandJianLL);
			nbt.putBoolean("oneHandJianRR", oneHandJianRR);
			nbt.putBoolean("oneHandJianRL", oneHandJianRL);
			nbt.putBoolean("oneHandJianLR", oneHandJianLR);
			nbt.putBoolean("howToUse", howToUse);
			nbt.putBoolean("useing", useing);
			nbt.putDouble("neiLiAdd", neiLiAdd);
			nbt.putDouble("shuLianDuLevel", shuLianDuLevel);
			nbt.putBoolean("mouseOrKey", mouseOrKey);
			nbt.putDouble("qingGong", qingGong);
			nbt.putDouble("quanFa", quanFa);
			nbt.putBoolean("quanFaLL", quanFaLL);
			nbt.putBoolean("quanFaRR", quanFaRR);
			nbt.putBoolean("quanFaRL", quanFaRL);
			nbt.putBoolean("quanFaLR", quanFaLR);
			nbt.putBoolean("NeiLiHud", NeiLiHud);
			return nbt;
		}

		public void readNBT(Tag Tag) {
			CompoundTag nbt = (CompoundTag) Tag;
			neiLi = nbt.getDouble("neiLi");
			neiLiMax = nbt.getDouble("neiLiMax");
			oneHandJian = nbt.getDouble("oneHandJian");
			oneHandJianLL = nbt.getBoolean("oneHandJianLL");
			oneHandJianRR = nbt.getBoolean("oneHandJianRR");
			oneHandJianRL = nbt.getBoolean("oneHandJianRL");
			oneHandJianLR = nbt.getBoolean("oneHandJianLR");
			howToUse = nbt.getBoolean("howToUse");
			useing = nbt.getBoolean("useing");
			neiLiAdd = nbt.getDouble("neiLiAdd");
			shuLianDuLevel = nbt.getDouble("shuLianDuLevel");
			mouseOrKey = nbt.getBoolean("mouseOrKey");
			qingGong = nbt.getDouble("qingGong");
			quanFa = nbt.getDouble("quanFa");
			quanFaLL = nbt.getBoolean("quanFaLL");
			quanFaRR = nbt.getBoolean("quanFaRR");
			quanFaRL = nbt.getBoolean("quanFaRL");
			quanFaLR = nbt.getBoolean("quanFaLR");
			NeiLiHud = nbt.getBoolean("NeiLiHud");
		}
	}

	public static class PlayerVariablesSyncMessage {
		public PlayerVariables data;

		public PlayerVariablesSyncMessage(FriendlyByteBuf buffer) {
			this.data = new PlayerVariables();
			this.data.readNBT(buffer.readNbt());
		}

		public PlayerVariablesSyncMessage(PlayerVariables data) {
			this.data = data;
		}

		public static void buffer(PlayerVariablesSyncMessage message, FriendlyByteBuf buffer) {
			buffer.writeNbt((CompoundTag) message.data.writeNBT());
		}

		public static void handler(PlayerVariablesSyncMessage message, Supplier<NetworkEvent.Context> contextSupplier) {
			NetworkEvent.Context context = contextSupplier.get();
			context.enqueueWork(() -> {
				if (!context.getDirection().getReceptionSide().isServer()) {
					PlayerVariables variables = ((PlayerVariables) Minecraft.getInstance().player.getCapability(PLAYER_VARIABLES_CAPABILITY, null)
							.orElse(new PlayerVariables()));
					variables.neiLi = message.data.neiLi;
					variables.neiLiMax = message.data.neiLiMax;
					variables.oneHandJian = message.data.oneHandJian;
					variables.oneHandJianLL = message.data.oneHandJianLL;
					variables.oneHandJianRR = message.data.oneHandJianRR;
					variables.oneHandJianRL = message.data.oneHandJianRL;
					variables.oneHandJianLR = message.data.oneHandJianLR;
					variables.howToUse = message.data.howToUse;
					variables.useing = message.data.useing;
					variables.neiLiAdd = message.data.neiLiAdd;
					variables.shuLianDuLevel = message.data.shuLianDuLevel;
					variables.mouseOrKey = message.data.mouseOrKey;
					variables.qingGong = message.data.qingGong;
					variables.quanFa = message.data.quanFa;
					variables.quanFaLL = message.data.quanFaLL;
					variables.quanFaRR = message.data.quanFaRR;
					variables.quanFaRL = message.data.quanFaRL;
					variables.quanFaLR = message.data.quanFaLR;
					variables.NeiLiHud = message.data.NeiLiHud;
				}
			});
			context.setPacketHandled(true);
		}
	}
}
